﻿using System;
using System.Collections.Generic;
using System.Linq;
using Nest;
using Newtonsoft.Json;

using ES.Client.Utility;

namespace ES.Client.Filter
{
    /// <summary>
    /// 嵌套对象的内部字段过滤条件
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class InnerNestedFieldBuilder<T> : BaseFilter<T>, IFilterBuilder where T : class
    {
        internal InnerNestedFieldBuilder(string index, string docType)
        {
            this.Index = index;
            this.DocType = docType;
        }

        protected override bool TryGetProperty(string field, out Nest.IProperty property)
        {
            property = null;

            Nest.ObjectProperty rootProperty = null;
            var rootField = FieldUtil.GetRootField(field);
            if (!base.Hub.TryGetObjectField(rootField, out rootProperty))
            {
                return false;
            }

            if (rootProperty == null)
            {
                return false;
            }

            var properties = rootProperty.Properties;
            if (properties == null || !properties.Any())
            {
                return false;
            }

            var fields = field.Split(new char[1] { '.' }, StringSplitOptions.RemoveEmptyEntries).Skip(1).ToList();
            var childField = fields[0];
            var childProperty = properties.FirstOrDefault(p => p.Key.Name == childField).Value;

            if (childProperty == null)
            {
                return false;
            }

            property = childProperty;
            return true;
        }

        public IEnumerable<QueryContainer> EQ<TValue>(string field, TValue value)
        {
            var rootField = FieldUtil.GetRootField(field);
            List<QueryContainer> containers = new List<QueryContainer>();
            if (value != null)
            {
                containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Term(f => f.Field(field).Value(value)))));
            }

            return containers;
        }

        public IEnumerable<QueryContainer> NE<TValue>(string field, TValue value)
        {
            List<QueryContainer> containers = new List<QueryContainer>();
            return containers;
        }

        /// <summary>
        /// Based on range filter, It only support number values and date values;
        /// </summary>
        /// <typeparam name="TValue"></typeparam>
        /// <param name="field"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public IEnumerable<QueryContainer> GT<TValue>(string field, TValue value)
        {
            var rootField = FieldUtil.GetRootField(field);
            List<QueryContainer> containers = new List<QueryContainer>();

            if (value == null)
            {
                return containers;
            }

            Nest.IProperty property = null;
            if (!this.TryGetProperty(field, out property))
            {
                return containers;
            }

            var type = property.Type.Name;
            switch (type)
            {
                case "short":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Range(r => r.Field(field).GreaterThan(Convert.ToInt16(value))))));
                    break;
                case "integer":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Range(r => r.Field(field).GreaterThan(Convert.ToInt32(value))))));
                    break;
                case "long":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Range(r => r.Field(field).GreaterThan(Convert.ToInt64(value))))));
                    break;
                case "byte":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Range(r => r.Field(field).GreaterThan(Convert.ToByte(value))))));
                    break;
                case "double":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Range(r => r.Field(field).GreaterThan(Convert.ToDouble(value))))));
                    break;
                case "float":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Range(r => r.Field(field).GreaterThan(Convert.ToSingle(value))))));
                    break;
                case "date":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.DateRange(r => r.Field(field).GreaterThan(Convert.ToDateTime(value))))));
                    break;
                default:
                    break;
            }

            return containers;
        }

        /// <summary>
        /// Based on range filter, It only support number values and date values;
        /// </summary>
        /// <typeparam name="TValue"></typeparam>
        /// <param name="field"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public IEnumerable<QueryContainer> GTE<TValue>(string field, TValue value)
        {
            var rootField = FieldUtil.GetRootField(field);
            List<QueryContainer> containers = new List<QueryContainer>();

            if (value == null)
            {
                return containers;
            }

            Nest.IProperty property = null;
            if (!this.TryGetProperty(field, out property))
            {
                return containers;
            }

            var type = property.Type.Name;
            switch (type)
            {
                case "short":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Range(r => r.Field(field).GreaterThanOrEquals(Convert.ToInt16(value))))));
                    break;
                case "integer":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Range(r => r.Field(field).GreaterThanOrEquals(Convert.ToInt32(value))))));
                    break;
                case "long":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Range(r => r.Field(field).GreaterThanOrEquals(Convert.ToInt64(value))))));
                    break;
                case "byte":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Range(r => r.Field(field).GreaterThanOrEquals(Convert.ToByte(value))))));
                    break;
                case "double":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Range(r => r.Field(field).GreaterThanOrEquals(Convert.ToDouble(value))))));
                    break;
                case "float":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Range(r => r.Field(field).GreaterThanOrEquals(Convert.ToSingle(value))))));
                    break;
                case "date":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.DateRange(r => r.Field(field).GreaterThanOrEquals(Convert.ToDateTime(value))))));
                    break;
                default:
                    break;
            }

            return containers;
        }

        /// <summary>
        /// Based on range filter, It only support number values and date values;
        /// </summary>
        /// <typeparam name="TValue"></typeparam>
        /// <param name="field"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public IEnumerable<QueryContainer> LT<TValue>(string field, TValue value)
        {
            var rootField = FieldUtil.GetRootField(field);
            List<QueryContainer> containers = new List<QueryContainer>();

            if (value == null)
            {
                return containers;
            }

            Nest.IProperty property = null;
            if (!this.TryGetProperty(field, out property))
            {
                return containers;
            }

            var type = property.Type.Name;
            switch (type)
            {
                case "short":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Range(r => r.Field(field).LessThan(Convert.ToInt16(value))))));
                    break;
                case "integer":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Range(r => r.Field(field).LessThan(Convert.ToInt32(value))))));
                    break;
                case "long":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Range(r => r.Field(field).LessThan(Convert.ToInt64(value))))));
                    break;
                case "byte":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Range(r => r.Field(field).LessThan(Convert.ToByte(value))))));
                    break;
                case "double":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Range(r => r.Field(field).LessThan(Convert.ToDouble(value))))));
                    break;
                case "float":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Range(r => r.Field(field).LessThan(Convert.ToSingle(value))))));
                    break;
                case "date":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.DateRange(r => r.Field(field).LessThan(Convert.ToDateTime(value))))));
                    break;
                default:
                    break;
            }

            return containers;
        }

        /// <summary>
        /// Based on range filter, It only support number values and date values;
        /// </summary>
        /// <typeparam name="TValue"></typeparam>
        /// <param name="field"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public IEnumerable<QueryContainer> LTE<TValue>(string field, TValue value)
        {
            var rootField = FieldUtil.GetRootField(field);
            List<QueryContainer> containers = new List<QueryContainer>();

            if (value == null)
            {
                return containers;
            }

            Nest.IProperty property = null;
            if (!this.TryGetProperty(field, out property))
            {
                return containers;
            }

            var type = property.Type.Name;
            switch (type)
            {
                case "short":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Range(r => r.Field(field).LessThanOrEquals(Convert.ToInt16(value))))));
                    break;
                case "integer":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Range(r => r.Field(field).LessThanOrEquals(Convert.ToInt32(value))))));
                    break;
                case "long":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Range(r => r.Field(field).LessThanOrEquals(Convert.ToInt64(value))))));
                    break;
                case "byte":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Range(r => r.Field(field).LessThanOrEquals(Convert.ToByte(value))))));
                    break;
                case "double":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Range(r => r.Field(field).LessThanOrEquals(Convert.ToDouble(value))))));
                    break;
                case "float":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Range(r => r.Field(field).LessThanOrEquals(Convert.ToSingle(value))))));
                    break;
                case "date":
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.DateRange(r => r.Field(field).LessThanOrEquals(Convert.ToDateTime(value))))));
                    break;
                default:
                    break;
            }

            return containers;
        }

        /// <summary>
        /// 判断文档是否包含某个字段，字段值既不等于null也不等于空字符串
        /// </summary>
        /// <param name="field"></param>
        /// <returns></returns>
        public IEnumerable<QueryContainer> Exists(string field)
        {
            var rootField = FieldUtil.GetRootField(field);
            List<QueryContainer> containers = new List<QueryContainer>();
            containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Exists(f => f.Field(field)))));
            return containers;
        }

        /// <summary>
        /// 通过给定字段获取文档，字段值既不等于null也不等于空字符串
        /// </summary>
        /// <param name="field"></param>
        /// <returns></returns>
        public IEnumerable<QueryContainer> IsNotNullAndEmpty(string field)
        {
            List<QueryContainer> containers = new List<QueryContainer>();
            return containers;
        }

        /// <summary>
        /// 判断文档是否不包含某个文档，字段值等于null或者等于空字符串
        /// </summary>
        /// <param name="field"></param>
        /// <returns></returns>
        public IEnumerable<QueryContainer> Missing(string field)
        {
            var rootField = FieldUtil.GetRootField(field);
            List<QueryContainer> containers = new List<QueryContainer>();
            containers.Add(Query<T>.Bool(b => b.MustNot(
                Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Exists(f => f.Field(field))))
            )));
            return containers;
        }

        /// <summary>
        /// 通过给定字段获取文档，字段值等于null或者等于空字符串
        /// </summary>
        /// <param name="field"></param>
        /// <returns></returns>
        public IEnumerable<QueryContainer> IsNullOrEmpty(string field)
        {
            List<QueryContainer> containers = new List<QueryContainer>();
            return containers;
        }

        /// <summary>
        /// 以特定前缀搜索
        /// </summary>
        /// <param name="field"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public IEnumerable<QueryContainer> Prefix(string field, string query)
        {
            var rootField = FieldUtil.GetRootField(field);
            List<QueryContainer> containers = new List<QueryContainer>();
            if (!string.IsNullOrWhiteSpace(query))
            {
                containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Prefix(p => p.Field(field).Value(query)))));
            }

            return containers;
        }

        /// <summary>
        /// 指定字段模糊搜索
        /// </summary>
        /// <param name="field"></param>
        /// <param name="keyword"></param>
        /// <returns></returns>
        public IEnumerable<QueryContainer> Like(string field, string keyword)
        {
            var rootField = FieldUtil.GetRootField(field);
            List<QueryContainer> containers = new List<QueryContainer>();
            if (!string.IsNullOrWhiteSpace(keyword))
            {
                IProperty property = null;
                if (this.TryGetProperty(field, out property) && property.Type.Name == "text")
                {
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Match(m => m.Field(new Field(field)).Query(keyword).Operator(Operator.And)))));
                    return containers;
                }

                var tokens = base.Hub.AnalyzeByField(field, keyword);

                if (!tokens.Any())
                {
                    return containers;
                }

                var target = tokens.Find(t => t.Token == keyword);
                if (target != null)
                {
                    tokens.Clear();
                    tokens.Add(target);
                }

                if (tokens.Count == 1)
                {
                    string _query = string.Format("*{0}*", tokens.FirstOrDefault().Token);
                    containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.QueryString(qs => qs.DefaultField(new Field(field)).Query(_query).AnalyzeWildcard(false).AutoGeneratePhraseQueries(false)))));
                    return containers;
                }

                List<string> _querys = new List<string>();
                for (int i = 0; i < tokens.Count; i++)
                {
                    var token = tokens[i];
                    if (token.Position == 0)
                    {
                        string _queryFirst = string.Format("(*{0} OR {0})", token.Token);
                        _querys.Add(_queryFirst);
                        continue;
                    }

                    if (token.EndOffset == keyword.Length)
                    {
                        string _queryLast = string.Format("({0}* OR {0})", token.Token);
                        _querys.Add(_queryLast);
                        continue; ;
                    }

                    string _query = string.Format("{0}", token.Token);
                    _querys.Add(_query);
                    continue;
                }

                var finalQuery = string.Join(" AND ", _querys);
                containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.QueryString(qs => qs.DefaultField(new Field(field)).Query(finalQuery).Fields(t => t.Field(field)).AnalyzeWildcard(false).AutoGeneratePhraseQueries(true).PhraseSlop(0.0)))));
            }

            return containers;
        }

        /// <summary>
        /// 正则表达式搜索
        /// </summary>
        /// <param name="field"></param>
        /// <param name="regex"></param>
        /// <returns></returns>
        public IEnumerable<QueryContainer> Regex(string field, string regex)
        {
            var rootField = FieldUtil.GetRootField(field);
            List<QueryContainer> containers = new List<QueryContainer>();
            if (!string.IsNullOrWhiteSpace(regex))
            {
                containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Regexp(p => p.Field(field).Value(regex)))));
            }

            return containers;
        }

        /// <summary>
        /// IN查询
        /// </summary>
        /// <typeparam name="TValue"></typeparam>
        /// <param name="field"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        public IEnumerable<QueryContainer> IN<TValue>(string field, IEnumerable<TValue> values)
        {
            var rootField = FieldUtil.GetRootField(field);
            List<QueryContainer> containers = new List<QueryContainer>();

            if (values == null)
            {
                return containers;
            }

            if (values.Count() == 0)
            {
                return containers;
            }

            containers.Add(Query<T>.Nested(n => n.Path(new Field(rootField)).Query(q => q.Terms(t => t.Field(field).Terms(values)))));
            return containers;
        }
    }
}
